package com.chen.base.utils.security;

import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

/**
 * AES加解密工具类
 * @author ChenJinLong
 */
public class AesUtil {

    /**
     * 秘钥, 必须由16位字符组成
     */
    public static final String SECRET_KEY = "CJL-0000LLLL-123";

    /**
     * AES加密
     * @param content 待加密的内容
     * @param secretKey 加密密钥
     * @return 加密后的内容
     */
    @SneakyThrows
    public static String encode(String content, String secretKey) {
        Cipher cipher = buildCipher(secretKey, Cipher.ENCRYPT_MODE);
        // 1.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
        byte[] byteEncode = content.getBytes(StandardCharsets.UTF_8);
        // 2.根据密码器的初始化方式--加密：将数据加密
        byte[] byteAes = cipher.doFinal(byteEncode);
        // 3.将加密后的数据转换为字符串
        return new BASE64Encoder().encode(byteAes);
    }

    /**
     * AES解密
     * @param content 待解密的内容
     * @param secretKey 解密密钥
     * @return 解密后的内容
     */
    @SneakyThrows
    public static String decode(String content, String secretKey) {
        Cipher cipher = buildCipher(secretKey, Cipher.DECRYPT_MODE);
        // 1.将加密并编码后的内容解码成字节数组
        byte[] byteContent = new BASE64Decoder().decodeBuffer(content);
        // 2.解密
        return new String(cipher.doFinal(byteContent), StandardCharsets.UTF_8).trim();
    }

    /**
     * AES加密
     * @param content 待加密的内容
     * @param secretKey 加密密钥
     * @return 加密后的byte[]
     */
    @SneakyThrows
    public static byte[] encryptToBytes(String content, String secretKey) {
        Cipher cipher = buildCipher(secretKey, Cipher.ENCRYPT_MODE);
        return cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * AES解密
     * @param encryptBytes 待解密的byte[]
     * @param secretKey 解密密钥
     * @return 解密后的String
     */
    @SneakyThrows
    public static String decryptByBytes(byte[] encryptBytes, String secretKey) {
        Cipher cipher = buildCipher(secretKey, Cipher.DECRYPT_MODE);
        return new String(cipher.doFinal(encryptBytes), StandardCharsets.UTF_8);
    }

    /**
     * 获取构建好的密码器，用于加密解密
     * TODO 随机源的位数可以是：128、192、256，越高表示越安全，但加解密时间越长，一般128足够用
     */
    @SneakyThrows
    private static Cipher buildCipher(String secretKey, int cipherMode) {
        // 1.构造密钥生成器，指定为AES算法,不区分大小写
        KeyGenerator gen = KeyGenerator.getInstance("AES");
        // 2.注意: 防止linux下 随机生成key。用其他方式在Windows上正常，但Linux上会有问题
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(secretKey.getBytes());
        // 3.根据 encryptKey规则初始化密钥生成器, 生成一个128位的随机源, 根据传入的字节数组
        gen.init(128, random);
        // 4.产生原始对称密钥,并获得原始对称密钥的字节数组
        byte[] encoded = gen.generateKey().getEncoded();
        // 5.根据字节数组生成AES密钥
        SecretKeySpec secretKeySpec = new SecretKeySpec(encoded, "AES");
        // 6.根据指定算法AES自成密码器: 算法/模式/补码方式
        Cipher cipher = Cipher.getInstance("AES");
        // 7.初始化密码器，第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作，第二个参数为使用的KEY
        cipher.init(cipherMode, secretKeySpec);
        return cipher;
    }

    /** CBC加密 **/
    @SneakyThrows
    public static String encodeCBC(String data, String secretKey){
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        int blockSize = cipher.getBlockSize();
        byte[] dataBytes = data.getBytes();
        int length = dataBytes.length;
        if (length % blockSize != 0){
            length = length + (blockSize - (length % blockSize));
        }

        byte[] plainText = new byte[length];
        System.arraycopy(dataBytes, 0, plainText, 0, dataBytes.length);

        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(secretKey.getBytes());

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        return Base64.encodeBase64String(cipher.doFinal(plainText));
    }

    /** CBC解密 **/
    @SneakyThrows
    public static String decodeCBC(String data, String secretKey){
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(secretKey.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] bytes = cipher.doFinal(Base64.decodeBase64(data));
        return new String(bytes, StandardCharsets.UTF_8).trim();
    }

    public static void main(String[] args) {
        String data = "D:\\T-PART\\e4e0bc18-a2e5-4b6c-aa47-6cfa13f36bf4\\ideaIU-2020.2.4.exe.S100E.part";
        String jiami = AesUtil.encodeCBC(data, AesUtil.SECRET_KEY);
//        jiami = URLEncoder.encode(jiami);
        System.out.println("加密: " + jiami);
//        jiami = URLDecoder.decode(jiami);
        System.out.println("解密: " + AesUtil.decodeCBC(jiami, AesUtil.SECRET_KEY));
    }
}